package com.mofum.msdom.excel.writer.impl;

import com.mofum.msdom.excel.builder.RowBuilder;
import com.mofum.msdom.excel.builder.SheetBuilder;
import com.mofum.msdom.excel.builder.StringCellBuilder;
import com.mofum.msdom.excel.builder.StyleBuilder;
import com.mofum.msdom.excel.constant.ExcelConfig;
import com.mofum.msdom.excel.constant.ExcelValue;
import com.mofum.msdom.excel.constant.ValueLevel;
import com.mofum.msdom.excel.converter.ExcelConverter;
import com.mofum.msdom.excel.converter.TypeConverter;
import com.mofum.msdom.excel.metadata.*;
import com.mofum.msdom.excel.utils.MetadataConvertUtils;
import com.mofum.msdom.excel.utils.StringUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class TypeExcelWriterImpl<Data> extends BaseWriterAdapter<Data> {

    public static Logger logger = LoggerFactory.getLogger(TypeExcelWriterImpl.class);

    /**
     * 输出流
     */
    protected OutputStream outputStream;

    /**
     * 工作簿
     */
    protected Workbook workbook;

    /**
     * 数据类型
     */
    protected Class<Data> dataClass;

    /**
     * 缓存内存的行数，超过的行数将存于硬盘上。
     */
    protected int cache;

    /**
     * 工作簿
     */
    protected MPWorkbook mpWorkbook;

    /**
     * 工作表
     */
    protected MPSheet[] sheets;

    /**
     * 工作薄索引
     */
    protected int sheetIndex = 0;

    /**
     * 列Map
     */
    protected Map<Field, MPColumn> columnMap;

    /**
     * 字段Map
     */
    protected Map<Integer, String> indexMap;

    /**
     * Excel 转换器
     */
    protected Map<Field, ExcelConverter> excelConverterMap;

    /**
     * Sheet上下文
     */
    protected Map<Integer, MPSheet> mpSheetMap;

    /**
     * Sheet上下文
     */
    protected Map<Integer, SheetContext> sheetContextMap;

    /**
     * 工作薄
     */
    protected Map<Integer, Sheet> sheetMap;

    /**
     * 启动状态
     */
    protected boolean startStatus = false;

    /**
     * Excel配置
     */
    protected ExcelConfig excelConfig;

    public TypeExcelWriterImpl() {
    }

    public TypeExcelWriterImpl(Data data, Map<String, Integer> columnMap) {
        try {
            this.mpWorkbook = MPWorkbook.DEFAULT_WORKBOOK;
            this.dataClass = (Class<Data>) data.getClass();
            this.columnMap = convertStringToColumn(this.dataClass, columnMap);
            this.indexMap = columnMapToIndexMap(this.columnMap);
            this.excelConverterMap = columnMapToExcelConverterMap(this.columnMap);
            this.workbook = new SXSSFWorkbook(mpWorkbook.getCache());
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    public TypeExcelWriterImpl(Class<Data> data, Map<Field, MPColumn> columnMap) {
        try {
            this.mpWorkbook = MPWorkbook.DEFAULT_WORKBOOK;
            this.dataClass = data;
            this.columnMap = columnMap;
            this.indexMap = columnMapToIndexMap(this.columnMap);
            this.excelConverterMap = columnMapToExcelConverterMap(columnMap);
            this.workbook = new SXSSFWorkbook(mpWorkbook.getCache());
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    public TypeExcelWriterImpl(Map<Field, MPColumn> columnMap, Map<Integer, String> indexMap, Map<Field, ExcelConverter> excelConverterMap) {
        this.mpWorkbook = MPWorkbook.DEFAULT_WORKBOOK;
        this.columnMap = columnMap;
        this.indexMap = indexMap;
        this.excelConverterMap = excelConverterMap;
        this.workbook = new SXSSFWorkbook(mpWorkbook.getCache());
    }

    public TypeExcelWriterImpl(MPWorkbook mpWorkbook, Map<Field, MPColumn> columnMap, Map<Integer, String> indexMap, Map<Field, ExcelConverter> excelConverterMap) {
        this.mpWorkbook = mpWorkbook;
        this.columnMap = columnMap;
        this.indexMap = indexMap;
        this.excelConverterMap = excelConverterMap;
        this.workbook = new SXSSFWorkbook(mpWorkbook.getCache());
    }

    @Override
    public TypeExcelWriterImpl start(OutputStream output) {
        if (dataClass == null) {
            throw new RuntimeException("Output data type must be set!");
        }
        setOutputStream(output);
        setStartStatus(true);
        return this;
    }

    @Override
    public TypeExcelWriterImpl end() {
        verificationStartSuccess();
        //导出后的完成工作
        try {
            workbook.write(outputStream);
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
            throw new RuntimeException(e.getMessage(), e);
        }

        try {
            outputStream.close();
            workbook.close();
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
            throw new RuntimeException(e.getMessage(), e);
        }

        if (workbook instanceof SXSSFWorkbook) {
            ((SXSSFWorkbook) workbook).dispose();
        }

        workbook = null;
        setStartStatus(false);
        return this;
    }

    /**
     * 校验是否初始化
     */
    private void verificationStartSuccess() {
        if (outputStream == null) {
            throw new RuntimeException("Not started, output  must be set!");
        }

        if (workbook == null) {
            throw new RuntimeException("Not started, workbook  must be set!");
        }
    }

    @Override
    public TypeExcelWriterImpl cache(int cache) {
        this.setCache(cache);
        if (workbook != null) {
            end();
        }
        workbook = new SXSSFWorkbook(cache);
        return this;
    }

    @Override
    public TypeExcelWriterImpl dataType(Class<Data> dataClass) {
        if (isStartStatus()) {
            logger.warn("Failed to change data model type ! Changing the data type must be done before it is started.");
            return this;
        }
        this.setDataClass(dataClass);
        return this;
    }

    protected Map<Field, MPColumn> convertStringToColumn(Class<Data> dataType, Map<String, Integer> columnMap) throws NoSuchFieldException, IllegalAccessException, InstantiationException {
        return MetadataConvertUtils.convertStringToColumn(dataType, columnMap);
    }

    protected Map<Field, TypeConverter> columnMapToConverterMap(Map<Field, MPColumn> columnMap) throws IllegalAccessException, InstantiationException {
        return MetadataConvertUtils.columnMapToConverterMap(columnMap, getExcelConfig().getConverter());
    }

    protected Map<Field, ExcelConverter> columnMapToExcelConverterMap(Map<Field, MPColumn> columnMap) throws IllegalAccessException, InstantiationException {
        return MetadataConvertUtils.columnMapToExcelConverterMap(columnMap, getExcelConfig().getExcelConverter());
    }

    /**
     * 字段Map转索引Map
     *
     * @param columnMap 字段Map值
     */
    protected Map<Integer, String> columnMapToIndexMap(Map<Field, MPColumn> columnMap) {
        return MetadataConvertUtils.columnMapToIndexMap(columnMap);
    }

    @Override
    public TypeExcelWriterImpl sheets(MPSheet... sheets) {

        if (sheets == null) {
            sheets = new MPSheet[]{MPSheet.DEFAUT_SHEET};
        }

        sheetContextMap = new HashMap<>();
        sheetMap = new HashMap<>();
        mpSheetMap = new HashMap<>();

        for (MPSheet mpSheet : sheets) {

            SheetContext sheetContext = new SheetContext();

            sheetContext.setLimit(mpSheet.getLimit());

            sheetContext.setOffset(mpSheet.getOffset());

            sheetContext.setRowHeight(mpSheet.getRowHeight());

            sheetContext.setCurrentRowIndex(-1);

            sheetContextMap.put(mpSheet.getIndex(), sheetContext);

            mpSheetMap.put(mpSheet.getIndex(), mpSheet);

            sheetMap.put(mpSheet.getIndex(), null);
        }

        setSheets(sheets);
        return this;
    }

    @Override
    public TypeExcelWriterImpl sheetIndex(int index) {
        this.setSheetIndex(index);
        return this;
    }

    @Override
    public <T> TypeExcelWriterImpl write(int sheetIndex, List<T> datas) {
        MPSheet mpSheet = mpSheetMap.get(sheetIndex);
        if (mpSheet != null) {
            sheetIndex(sheetIndex);
            return write(datas);
        }
        return this;
    }

    @Override
    public TypeExcelWriterImpl addMergeRegion(MPMergeRange range) {
        Sheet sheet = sheetMap.get(sheetIndex);
        sheet.addMergedRegion(new CellRangeAddress(range.getStartRow(), range.getEndRow(), range.getStartCol(), range.getEndCol()));
        return this;
    }

    @Override
    public TypeExcelWriterImpl addMergeRegion(CellRangeAddress range) {
        Sheet sheet = sheetMap.get(sheetIndex);
        sheet.addMergedRegion(range);
        return this;
    }

    @Override
    public TypeExcelWriterImpl addMergeRegion(int firstRow, int lastRow, int firstCol, int lastCol) {
        Sheet sheet = sheetMap.get(sheetIndex);
        sheet.addMergedRegion(new CellRangeAddress(firstRow, lastRow, firstCol, lastCol));
        return this;
    }

    @Override
    public <T> TypeExcelWriterImpl write(List<T> datas) {
        verificationStartSuccess();
        if (datas == null) {
            return this;
        }
        writeRowsData(datas);
        return this;
    }

    /**
     * 写多行数据
     *
     * @param datas 数据
     */
    private <T> void writeRowsData(List<T> datas) {
        MPSheet mpSheet = mpSheetMap.get(sheetIndex);
        if (mpSheet == null) {
            return;
        }
        Sheet sheet = this.createSheet(mpSheet);

        SheetContext sheetContext = sheetContextMap.get(sheetIndex);

        for (T data : datas) {
            sheetContext.setCurrentRowIndex((sheetContext.getCurrentRowIndex() + 1));
            writeSingeRowData(sheet, data);
        }
    }

    /**
     * 创建 工具表格
     *
     * @param mpSheet 工作表信息
     *                工作表格
     */
    private Sheet createSheet(MPSheet mpSheet) {
        Sheet sheet = sheetMap.get(mpSheet.getIndex());
        if (sheet != null) {
            return sheet;
        }

        SheetBuilder sheetBuilder = new SheetBuilder(workbook);
        sheetBuilder.newSheet(mpSheet.getName());
        if (mpSheet.isAutoSize()) {
            //自动设置大小
            for (Field field : columnMap.keySet()) {
                MPColumn mpColumn = columnMap.get(field);
                sheetBuilder.trackColumnForAutoSizing(mpColumn.getIndex());
            }
        }

        //设置宽度
        for (Field field : columnMap.keySet()) {
            MPColumn mpColumn = columnMap.get(field);

            if (mpColumn.getWidth() > 0) {
                sheetBuilder.columnWidth(mpColumn.getIndex(), mpColumn.getWidth());
            }
        }

        sheet = sheetBuilder.build();

        for (MPMergeRange range : mpSheet.getRanges()) {
            sheetBuilder.mergedRegion(range);
            if (ValueLevel.REPLACE.equals(range.getLevel())) {
                //赋值
                //赋值样式
            }
        }

        //创建表头
        MPHeaderRow[] headerRows = mpSheet.getHeaderRows();
        if (headerRows != null) {
            SheetContext sheetContext = sheetContextMap.get(mpSheet.getIndex());
            sheetContext.setCurrentRowIndex(headerRows.length - 1);

            for (MPHeaderRow mpHeaderRow : headerRows) {
                if (mpHeaderRow.isRequired()) {
                    //创建表头
                    createHeaderRow(mpHeaderRow, sheet);
                }
            }

        }


        sheetMap.put(mpSheet.getIndex(), sheet);
        return sheet;
    }

    private void createHeaderRow(MPHeaderRow mpHeaderRow, Sheet sheet) {
        if (mpHeaderRow.getColumns() == null) {
            return;
        }
        RowBuilder rowBuilder = new RowBuilder(sheet);

        Row row = rowBuilder.newRow(mpHeaderRow.getIndex()).build();

        for (MPHeaderColumn mpHeaderColumn : mpHeaderRow.getColumns()) {

            ExcelValue excelValue = new ExcelValue();
            excelValue.setType(CellType.STRING);
            excelValue.setValue(mpHeaderColumn.getValue());
            MPColumn mpColumn = new MPColumn();

            mpColumn.setIndex(mpHeaderColumn.getIndex());
            mpColumn.setStyles(mpHeaderColumn.getStyles());
            createCellData(excelValue, mpColumn, row);
        }

    }

    /**
     * 写单行数据
     *
     * @param sheet 工作薄
     * @param data  数据
     */
    private <T> void writeSingeRowData(Sheet sheet, T data) {
        if (data == null) {
            return;
        }
        if (sheet == null) {
            return;
        }

        createRow(sheet, data);

    }

    /**
     * 创建行数据
     *
     * @param sheet 工作表
     * @param data  数据
     */
    private <T> void createRow(Sheet sheet, T data) {

        RowBuilder rowBuilder = new RowBuilder(sheet);

        SheetContext sheetContext = sheetContextMap.get(sheetIndex);

        Row row = rowBuilder.newRow(sheetContext.currentRowIndex).height(sheetContext.getRowHeight()).build();

        try {

            for (Field field : columnMap.keySet()) {
                MPColumn mpColumn = columnMap.get(field);
                if (mpColumn == null) {
                    continue;
                }
                field.setAccessible(true);

                Object value = field.get(data);
                field.setAccessible(false);

                ExcelConverter excelConverter = excelConverterMap.get(field);
                ExcelValue cellValue = excelConverter.convert(value);

                createCellData(cellValue, mpColumn, row);

            }

        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            throw new RuntimeException(e.getMessage(), e);
        }

    }

    /**
     * 创建Cell 数据
     *
     * @param value    数据值
     * @param mpColumn 列相关信息
     * @param row      行
     */
    private void createCellData(ExcelValue value, MPColumn mpColumn, Row row) {
        StringCellBuilder cellBuilder = new StringCellBuilder(row);
        MPStyle style = null;
        if (mpColumn.getStyles() != null && mpColumn.getStyles().length > 0) {
            style = mpColumn.getStyles()[0];
        }

        cellBuilder.newCell(mpColumn.getIndex()).type(value.getType()).value(value.getValue()).build();

        if (style != null) {
            StyleBuilder styleBuilder = new StyleBuilder(workbook);
            if (value.getDataFormat() != null && "".equals(value.getDataFormat())) {
                styleBuilder.dataFormat(value.getDataFormat());
            }
            CellStyle cellStyle = styleBuilder.mpStyle(style).build();
            cellBuilder.style(cellStyle);
        }

        if (mpColumn.getWidth() <= 0) {
            int width = row.getSheet().getColumnWidth(mpColumn.getIndex());

            int colWidth = StringUtils.stringLength(String.valueOf(value));

            if (colWidth > width) {
                row.getSheet().setColumnWidth(mpColumn.getIndex(), StringUtils.stringLength(String.valueOf(value)));
            }

        }
    }

    public OutputStream getOutputStream() {
        return outputStream;
    }

    public void setOutputStream(OutputStream outputStream) {
        this.outputStream = outputStream;
    }

    public Workbook getWorkbook() {
        return workbook;
    }

    public void setWorkbook(Workbook workbook) {
        this.workbook = workbook;
    }

    public Class<Data> getDataClass() {
        return dataClass;
    }

    public void setDataClass(Class<Data> dataClass) {
        this.dataClass = dataClass;
    }

    public int getCache() {
        return cache;
    }

    public void setCache(int cache) {
        this.cache = cache;
    }

    public MPWorkbook getMpWorkbook() {
        return mpWorkbook;
    }

    public void setMpWorkbook(MPWorkbook mpWorkbook) {
        this.mpWorkbook = mpWorkbook;
    }

    public MPSheet[] getSheets() {
        return sheets;
    }

    public void setSheets(MPSheet[] sheets) {
        this.sheets = sheets;
    }

    public int getSheetIndex() {
        return sheetIndex;
    }

    public void setSheetIndex(int sheetIndex) {
        this.sheetIndex = sheetIndex;
    }

    public Map<Field, MPColumn> getColumnMap() {
        return columnMap;
    }

    public void setColumnMap(Map<Field, MPColumn> columnMap) {
        this.columnMap = columnMap;
    }

    public Map<Integer, String> getIndexMap() {
        return indexMap;
    }

    public void setIndexMap(Map<Integer, String> indexMap) {
        this.indexMap = indexMap;
    }

    public Map<Field, ExcelConverter> getExcelConverterMap() {
        return excelConverterMap;
    }

    public void setExcelConverterMap(Map<Field, ExcelConverter> excelConverterMap) {
        this.excelConverterMap = excelConverterMap;
    }

    public Map<Integer, MPSheet> getMpSheetMap() {
        return mpSheetMap;
    }

    public void setMpSheetMap(Map<Integer, MPSheet> mpSheetMap) {
        this.mpSheetMap = mpSheetMap;
    }

    public Map<Integer, SheetContext> getSheetContextMap() {
        return sheetContextMap;
    }

    public void setSheetContextMap(Map<Integer, SheetContext> sheetContextMap) {
        this.sheetContextMap = sheetContextMap;
    }

    public Map<Integer, Sheet> getSheetMap() {
        return sheetMap;
    }

    public void setSheetMap(Map<Integer, Sheet> sheetMap) {
        this.sheetMap = sheetMap;
    }

    /**
     * 工作表上下文
     */
    private class SheetContext {
        /**
         * 界限
         */
        private int limit;

        /**
         * 偏移量
         */
        private int offset;
        /**
         * 当前行
         */
        private int currentRowIndex;
        /**
         * 行高
         */
        private short rowHeight;

        public int getLimit() {
            return limit;
        }

        public void setLimit(int limit) {
            this.limit = limit;
        }

        public int getOffset() {
            return offset;
        }

        public void setOffset(int offset) {
            this.offset = offset;
        }

        public int getCurrentRowIndex() {
            return currentRowIndex;
        }

        public void setCurrentRowIndex(int currentRowIndex) {
            this.currentRowIndex = currentRowIndex;
        }

        public short getRowHeight() {
            return rowHeight;
        }

        public void setRowHeight(short rowHeight) {
            this.rowHeight = rowHeight;
        }
    }

    public boolean isStartStatus() {
        return startStatus;
    }

    public void setStartStatus(boolean startStatus) {
        this.startStatus = startStatus;
    }

    public ExcelConfig getExcelConfig() {
        if (excelConfig == null) {
            return ExcelConfig.DEFAULT_EXCEL_CONFIG;
        }
        return excelConfig;
    }

    public void setExcelConfig(ExcelConfig excelConfig) {
        this.excelConfig = excelConfig;
    }
}
